﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace Yaml.Presentation.Parsing.TokenScanners {
    class DirectiveTokenScannerException : ScannerException {
        public DirectiveTokenScannerException(string expected, Mark start, Mark end)
            : base("while scanning a directive starting at " + start.ToString() + " " + expected + " at " + end.ToString()) {
        }
        public DirectiveTokenScannerException(string expected, Mark start, Mark end, Exception inner)
            : base("while scanning a directive starting at " + start.ToString() + " " + expected + " at " + end.ToString(), inner) {
        }
    }

    class DirectiveTokenScanner : TokenScanner {
        string name;
        string[] values;

        public DirectiveTokenScanner(Scanner scanner)
            : base(scanner) {
            name = null;
            values = new string[2];
        }

        protected override bool CheckForToken() {
            // Directives start with % at the beginning of a line.
            return Reader.Peek() == '%' && Scanner.State.Column == 0;
        }

        protected override Yaml.Presentation.Tokens.Token CreateToken() {
            switch (name) {
                case "YAML":
                    return new Tokens.YamlDirectiveToken(Start, End, values[0], values[1]);
                case "TAG":
                    return new Tokens.TagDirectiveToken(Start, End, values[0], values[1]);
            }
            return null;
        }

        protected override void ScanForToken() {
            Scanner.Forward(1);
            ScanName();
            Scanner.SkipSpaces();
            if (name == "YAML") {
                ScanYAMLValue();
            } else if (name == "TAG") {
                ScanTagValue();
            } else {
                Scanner.Warning("Reserved Directive used: " + name + "at " + Scanner.GetMark().ToString());
            }
            Scanner.SkipToLineBreak();
        }

        protected override void AfterScan() {
            base.AfterScan();
            Scanner.SkipComment();
        }

        private void ScanName() {
            int length = 0;
            StringBuilder nameStr = new StringBuilder();
            char ch = Reader.Peek();
            while (Scanner.IsAlphanumeric(ch)) {
                nameStr.Append(ch);
                length++;
                Scanner.Forward(1);
                ch = Reader.Peek();
            }
            if (length == 0 || ch!=' ') {
                throw new DirectiveTokenScannerException("expected alphanumeric character but found '" + ch + "'", Start, Scanner.GetMark());
            }
            name = nameStr.ToString();
        }

        private void ScanYAMLValue() {
            Scanner.SkipSpaces();
            // Get the major version number
            values[0] = ScanYAMLVersionNumber();
            // Get the . between the version numbers.
            if (Reader.Peek() != '.') {
                throw new DirectiveTokenScannerException("expected a digit or '.' but found '" + Reader.Peek() + "'", Start, Scanner.GetMark());
            }
            Scanner.Forward(1);
            // Get the minor version number
            values[1] = ScanYAMLVersionNumber();
            if (!Scanner.IsLineBreakOrNullChar(Reader.Peek())) {
                throw new DirectiveTokenScannerException("expected alphanumeric character but found '" + Reader.Peek() + "'", Start, Scanner.GetMark());
            }
            Scanner.State.SetYAMLVersion(int.Parse(values[0]), int.Parse(values[1]));
        }

        private string ScanYAMLVersionNumber() {
            StringBuilder number = new StringBuilder();
            char ch = Reader.Peek();
            if ( !Scanner.IsDigit(ch) ) {
                throw new DirectiveTokenScannerException("expected a digit character but found '" + Reader.Peek() + "'", Start, Scanner.GetMark());
            }
            while( Scanner.IsDigit(ch) ) {
                number.Append(ch);
                Scanner.Forward(1);
                ch = Reader.Peek();
            }
            return number.ToString();
        }

        private void ScanTagValue() {
            Scanner.SkipSpaces();
            values[0] = ScanDirectiveHandle();
            Scanner.SkipSpaces();
            values[1] = ScanDirectivePrefix();
        }

        private string ScanDirectiveHandle() {
            string handle;
            try {
                handle = ScanTagHandle();
            } catch (ScannerException x) {
                throw new DirectiveTokenScannerException(x.Message, Start, Scanner.GetMark());
            }
            char ch = Reader.Peek();
            if (ch != ' ') {
                throw new DirectiveTokenScannerException("expected ' ' but found '" + ch + "'", Start, Scanner.GetMark());
            }
            return handle;
        }

        private string ScanDirectivePrefix() {
            string prefix;
            try {
                prefix = ScanTagContent();
            } catch (ScannerException x) {
                throw new DirectiveTokenScannerException(x.Message, Start, Scanner.GetMark());
            }
            char ch = Reader.Peek();
            if (!Scanner.IsLineBreakOrNullChar(ch)) {
                throw new DirectiveTokenScannerException("expected ' ' but found '" + ch + "'", Start, Scanner.GetMark());
            }
            return prefix;
        }
    }
}
